/*
 * Copyright (C) 2008 Esmertec AG.
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.mms.util;

import android.content.ContentUris;
import android.content.UriMatcher;
import android.net.Uri;
import android.provider.Telephony.Mms;
import android.util.Config;
import android.util.Log;

import java.util.HashMap;
import java.util.HashSet;

public final class PduCache extends AbstractCache<Uri, PduCacheEntry> {
	private static final String TAG = "PduCache";
	private static final boolean DEBUG = false;
	private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;

	private static final int MMS_ALL = 0;
	private static final int MMS_ALL_ID = 1;
	private static final int MMS_INBOX = 2;
	private static final int MMS_INBOX_ID = 3;
	private static final int MMS_SENT = 4;
	private static final int MMS_SENT_ID = 5;
	private static final int MMS_DRAFTS = 6;
	private static final int MMS_DRAFTS_ID = 7;
	private static final int MMS_OUTBOX = 8;
	private static final int MMS_OUTBOX_ID = 9;
	private static final int MMS_CONVERSATION = 10;
	private static final int MMS_CONVERSATION_ID = 11;

	private static final UriMatcher URI_MATCHER;
	private static final HashMap<Integer, Integer> MATCH_TO_MSGBOX_ID_MAP;

	private static PduCache sInstance;

	static {
		URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
		URI_MATCHER.addURI("mms", null, MMS_ALL);
		URI_MATCHER.addURI("mms", "#", MMS_ALL_ID);
		URI_MATCHER.addURI("mms", "inbox", MMS_INBOX);
		URI_MATCHER.addURI("mms", "inbox/#", MMS_INBOX_ID);
		URI_MATCHER.addURI("mms", "sent", MMS_SENT);
		URI_MATCHER.addURI("mms", "sent/#", MMS_SENT_ID);
		URI_MATCHER.addURI("mms", "drafts", MMS_DRAFTS);
		URI_MATCHER.addURI("mms", "drafts/#", MMS_DRAFTS_ID);
		URI_MATCHER.addURI("mms", "outbox", MMS_OUTBOX);
		URI_MATCHER.addURI("mms", "outbox/#", MMS_OUTBOX_ID);
		URI_MATCHER.addURI("mms-sms", "conversations", MMS_CONVERSATION);
		URI_MATCHER.addURI("mms-sms", "conversations/#", MMS_CONVERSATION_ID);

		MATCH_TO_MSGBOX_ID_MAP = new HashMap<Integer, Integer>();
		MATCH_TO_MSGBOX_ID_MAP.put(MMS_INBOX, Mms.MESSAGE_BOX_INBOX);
		MATCH_TO_MSGBOX_ID_MAP.put(MMS_SENT, Mms.MESSAGE_BOX_SENT);
		MATCH_TO_MSGBOX_ID_MAP.put(MMS_DRAFTS, Mms.MESSAGE_BOX_DRAFTS);
		MATCH_TO_MSGBOX_ID_MAP.put(MMS_OUTBOX, Mms.MESSAGE_BOX_OUTBOX);
	}

	private final HashMap<Integer, HashSet<Uri>> mMessageBoxes;
	private final HashMap<Long, HashSet<Uri>> mThreads;

	private PduCache() {
		mMessageBoxes = new HashMap<Integer, HashSet<Uri>>();
		mThreads = new HashMap<Long, HashSet<Uri>>();
	}

	synchronized public static final PduCache getInstance() {
		if (sInstance == null) {
			if (LOCAL_LOGV) {
				Log.v(TAG, "Constructing new PduCache instance.");
			}
			sInstance = new PduCache();
		}
		return sInstance;
	}

	@Override
	synchronized public boolean put(Uri uri, PduCacheEntry entry) {
		int msgBoxId = entry.getMessageBox();
		HashSet<Uri> msgBox = mMessageBoxes.get(msgBoxId);
		if (msgBox == null) {
			msgBox = new HashSet<Uri>();
			mMessageBoxes.put(msgBoxId, msgBox);
		}

		long threadId = entry.getThreadId();
		HashSet<Uri> thread = mThreads.get(threadId);
		if (thread == null) {
			thread = new HashSet<Uri>();
			mThreads.put(threadId, thread);
		}

		Uri finalKey = normalizeKey(uri);
		boolean result = super.put(finalKey, entry);
		if (result) {
			msgBox.add(finalKey);
			thread.add(finalKey);
		}
		return result;
	}

	@Override
	synchronized public PduCacheEntry purge(Uri uri) {
		int match = URI_MATCHER.match(uri);
		switch (match) {
		case MMS_ALL_ID:
			return purgeSingleEntry(uri);
		case MMS_INBOX_ID:
		case MMS_SENT_ID:
		case MMS_DRAFTS_ID:
		case MMS_OUTBOX_ID:
			String msgId = uri.getLastPathSegment();
			return purgeSingleEntry(Uri.withAppendedPath(Mms.CONTENT_URI, msgId));
			// Implicit batch of purge, return null.
		case MMS_ALL:
		case MMS_CONVERSATION:
			purgeAll();
			return null;
		case MMS_INBOX:
		case MMS_SENT:
		case MMS_DRAFTS:
		case MMS_OUTBOX:
			purgeByMessageBox(MATCH_TO_MSGBOX_ID_MAP.get(match));
			return null;
		case MMS_CONVERSATION_ID:
			purgeByThreadId(ContentUris.parseId(uri));
			return null;
		default:
			return null;
		}
	}

	private PduCacheEntry purgeSingleEntry(Uri key) {
		PduCacheEntry entry = super.purge(key);
		if (entry != null) {
			removeFromThreads(key, entry);
			removeFromMessageBoxes(key, entry);
			return entry;
		}
		return null;
	}

	@Override
	synchronized public void purgeAll() {
		super.purgeAll();

		mMessageBoxes.clear();
		mThreads.clear();
	}

	/**
	 * @param uri
	 *            The Uri to be normalized.
	 * @return Uri The normalized key of cached entry.
	 */
	private Uri normalizeKey(Uri uri) {
		int match = URI_MATCHER.match(uri);
		Uri normalizedKey = null;

		switch (match) {
		case MMS_ALL_ID:
			normalizedKey = uri;
			break;
		case MMS_INBOX_ID:
		case MMS_SENT_ID:
		case MMS_DRAFTS_ID:
		case MMS_OUTBOX_ID:
			String msgId = uri.getLastPathSegment();
			normalizedKey = Uri.withAppendedPath(Mms.CONTENT_URI, msgId);
			break;
		default:
			return null;
		}

		if (LOCAL_LOGV) {
			Log.v(TAG, uri + " -> " + normalizedKey);
		}
		return normalizedKey;
	}

	private void purgeByMessageBox(Integer msgBoxId) {
		if (LOCAL_LOGV) {
			Log.v(TAG, "Purge cache in message box: " + msgBoxId);
		}

		if (msgBoxId != null) {
			HashSet<Uri> msgBox = mMessageBoxes.remove(msgBoxId);
			if (msgBox != null) {
				for (Uri key : msgBox) {
					PduCacheEntry entry = super.purge(key);
					if (entry != null) {
						removeFromThreads(key, entry);
					}
				}
			}
		}
	}

	private void removeFromThreads(Uri key, PduCacheEntry entry) {
		HashSet<Uri> thread = mThreads.get(entry.getThreadId());
		if (thread != null) {
			thread.remove(key);
		}
	}

	private void purgeByThreadId(long threadId) {
		if (LOCAL_LOGV) {
			Log.v(TAG, "Purge cache in thread: " + threadId);
		}

		HashSet<Uri> thread = mThreads.remove(threadId);
		if (thread != null) {
			for (Uri key : thread) {
				PduCacheEntry entry = super.purge(key);
				if (entry != null) {
					removeFromMessageBoxes(key, entry);
				}
			}
		}
	}

	private void removeFromMessageBoxes(Uri key, PduCacheEntry entry) {
		HashSet<Uri> msgBox = mThreads.get(entry.getMessageBox());
		if (msgBox != null) {
			msgBox.remove(key);
		}
	}
}
